Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor code to make the API harder to misuse #261

Open
wants to merge 17 commits into
base: master
Choose a base branch
from

Conversation

MarcoPolo
Copy link
Contributor

@MarcoPolo MarcoPolo commented Jan 11, 2025

This seeks to refactor the codebase to make it much harder to hit nil pointer dereference panics.

This takes a different approach to how we've treated multiaddrs in the past. Instead of attempting to make them a general and performant datastructure, we focus on treating them as just an encoding scheme. Users of multiaddrs are expected to parse the multiaddr into some struct that is suitable for their use case, and use the multiaddr form when interoperating. By treating Multiaddrs as just an encoding scheme we can make a number of simplifications in the codebase. Specifically this PR does the following:

  • Removes the Multiaddrinterface.
  • Multiaddr is now a concrete type of []Component
  • Componentis now a public type, and users can use Components directly
  • Components no longer implement the Multiaddrinterface as there is none.

Background

This library has had multiple issues related to Multiaddr being an interface. Many methods use and return nil as the zero value, which behaves poorly when the user forgets to do a nil check on every returned value and attempts to call a method on the nil pointer. For example, using Split to split a Multiaddr and then using Join to rebuild the original Multiaddr historically would panic in case one side of the split was nil. Using an interface also leads to incorrect usages of == to check if two Multiaddrs were equal (would only work for pointer equality) and incorrectly using Multiaddr as a key for a map.

Using an interface is typically done to provide a consistent API surface for multiple implementing types. In practice however, the Multiaddr interface was only implemented for multiaddr and component (with arguably some awkwardness when using a component as a Multiaddr).

The better approach is to use a concrete type for a Multiaddr. This lets pointer receiver methods work even if the pointer is nil, since the compiler already knows which function to call. Most methods now take a value rather than a pointer which avoids the issue of a nil pointer dereference completely.

Migration

Refer to ./v015-MIGRATION.md for breaking changes and migration tips

@MarcoPolo MarcoPolo marked this pull request as ready for review January 21, 2025 18:45
@MarcoPolo MarcoPolo requested a review from sukunrt January 21, 2025 18:45
multiaddr.go Outdated Show resolved Hide resolved
multiaddr.go Show resolved Hide resolved
@@ -70,135 +62,61 @@ func StringCast(s string) Multiaddr {
}

// SplitFirst returns the first component and the rest of the multiaddr.
func SplitFirst(m Multiaddr) (*Component, Multiaddr) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's better to return (Multiaddr, Multiaddr)
The first multiaddr will be []Component{first}

With the current API, most users will need to do component.AsMultiaddr here.

This does break users who were expecting a component, but I believe that's the less popular usecase.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made this change and backed it out. I think a lot of usages was to get the component out, not a Multiaddr.

For example this is a pretty common pattern:

transport, p2ppart := ma.SplitLast(m)
if p2ppart == nil || p2ppart.Protocol().Code != ma.P_P2P {
    return m, ""
}

If we return a Multiaddr type, you can't use the Protocol() method like you were able to before.

This pattern mostly still works after this refactor, you just have to check p2ppart.Empty() instead of == nil

@MarcoPolo MarcoPolo force-pushed the marco/multiaddr-refactor branch from c652d4a to f43a278 Compare January 29, 2025 19:56
@MarcoPolo MarcoPolo requested a review from sukunrt January 29, 2025 19:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants